dojo.provide("miaigo.model.sgf.SGFStateMachine");

dojo.declare(
    "miaigo.model.sgf.SGFStateMachine",
    null,
    {
	states: {},
	currentState: "not-started",
	buffer: "",

	initialize: function()
	{
	    this.states =
		{
		    "not-started": new miaigo.model.sgf._NotStartedState(this),
		    "tree-started": new miaigo.model.sgf._TreeStartedState(this),
		    "tree-ended": new miaigo.model.sgf._TreeEndedState(this),
		    "node-started": new miaigo.model.sgf._NodeStartedState(this),
		    "prop-started": new miaigo.model.sgf._PropStartedState(this),
		    "value-started": new miaigo.model.sgf._ValueStartedState(this),
		    "value-ended": new miaigo.model.sgf._ValueEndedState(this)
		};
	    this.currentState = "not-started";
	    this.clearBuffer();
	},

	inputChar: function(ch)
	{
	    this.states[this.currentState].inputChar(ch);
	},

	switchState: function(state)
	{
	    if (this.states[state])
	    {
		var before = this.currentState;
		this.currentState = state;

		this.onStateChanged(before, this.currentState);
	    }
	    else
	    {
		console.error("Unknown State: " + state);
	    }
	},

	onStateChanged: function(before, after)
	{
	    this.states[before].onLeave();
	    this.states[after].onEnter();
	},

	/* To be connected */
	onTreeStarted: function() {},
	onTreeEnded: function() {},
	onNodeStarted: function() {},
	onProp: function(propID) {},
	onValue: function(value) {},

	clearBuffer: function()
	{
	    var bufferResult = this.buffer;
	    this.buffer = "";
	    return bufferResult;
	},

	bufferChar: function(ch)
	{
	    if (this.buffer.length > 0)
	    {
		this.buffer = this.buffer + ch;
	    }
	    else
		this.buffer = ch;
	}
    });

dojo.declare(
    "miaigo.model.sgf._State",
    null,
    {
	machine: null,

	constructor: function(machine)
	{
	    this.machine = machine;
	},

	/* Override */
	inputChar: function(ch)
	{},

	/* Override - When the state was switched to */
	onEnter: function()
	{},

	/* Override - When the state was switched out */
	onLeave: function()
	{},

	isWhiteSpace: function(ch)
	{
	    switch (ch)
	    {
	    case '\r':
	    case '\n':
	    case ' ':
	    case '\t':
		return true;
	    default:
		return false;
	    }
	},

	reportError: function(level, error)
	{
	    if (level == "normal")
	    {
		console.warn(error);
	    }
	    else if (level == "fatal")
	    {
		console.error(error);
		debugger;
	    }
	}
    });

dojo.declare(
    "miaigo.model.sgf._NotStartedState",
    [miaigo.model.sgf._State],
    {
	inputChar: function(ch)
	{
	    if (ch == '(')
	    {
		this.machine.switchState("tree-started");
	    }
	    else
	    {
		this.reportError("normal", "Character ignored");
	    }
	}
    });

dojo.declare(
    "miaigo.model.sgf._TreeStartedState",
    [miaigo.model.sgf._State],
    {
	inputChar: function(ch)
	{
	    if (ch == ';')
	    {
		this.machine.switchState("node-started");
	    }
	    else
	    {
		this.reportError("normal", "Character ignored");
	    }
	},

	onEnter: function()
	{
	    this.machine.onTreeStarted();
	}
    });

dojo.declare(
    "miaigo.model.sgf._TreeEndedState",
    [miaigo.model.sgf._State],
    {
	inputChar: function(ch)
	{
	    switch (ch)
	    {
	    case '(':
		this.machine.switchState("tree-started");
		break;
	    case ')':
		this.machine.switchState("tree-ended");
		break;
	    case ';':
		this.machine.switchState("node-started");
		break;
	    default:
		if (!this.isWhiteSpace(ch))
		{
		    this.reportError("fatal", "Unexpected Character");
		}
	    }
	},

	onEnter: function()
	{
	    this.machine.onTreeEnded();
	}
    });

dojo.declare(
    "miaigo.model.sgf._NodeStartedState",
    [miaigo.model.sgf._State],
    {
	inputChar: function(ch)
	{
	    if (ch >= 'A' && ch <= 'Z')
	    {
		this.machine.bufferChar(ch);
		this.machine.switchState("prop-started");
	    }
	    else if (this.isWhiteSpace(ch))
	    {
		// ignore white space
	    }
	    else
	    {
		this.reportError("fatal", "Unexpected Character, expecting A-Z");
	    }
	},

	onEnter: function()
	{
	    this.machine.onNodeStarted();
	}
    });

dojo.declare(
    "miaigo.model.sgf._PropStartedState",
    [miaigo.model.sgf._State],
    {
	inputChar: function(ch)
	{
	    if (ch >= 'A' && ch <= 'Z')
	    {
		this.machine.bufferChar(ch);
	    }
	    else if (ch == '[')
	    {
		this.machine.switchState("value-started");
	    }
	    else
	    {
		
		this.reportError("fatal", "Unexpected Character, expecting A-Z or '['");
	    }
	},

	onLeave: function()
	{
	    var propID = this.machine.clearBuffer();
	    this.machine.onProp(propID);
	}
    });

dojo.declare(
    "miaigo.model.sgf._ValueStartedState",
    [miaigo.model.sgf._State],
    {
	_escapeNext: false,

	inputChar: function(ch)
	{
	    if (ch == ']')
	    {
		if (this._escapeNext)
		{
		    this.machine.bufferChar(ch);
		    this._escapeNext = false;
		}
		else
		{
		    this.machine.switchState("value-ended");
		}
	    }
	    else if (ch == '\\')
	    {
		this._escapeNext = true;
	    }
	    else
	    {
		this.machine.bufferChar(ch);
		this._escapeNext = false;
	    }
	},

	onLeave: function()
	{
	    var value = this.machine.clearBuffer();
	    this.machine.onValue(value);
	}
    });

dojo.declare(
    "miaigo.model.sgf._ValueEndedState",
    [miaigo.model.sgf._State],
    {
	inputChar: function(ch)
	{
	    switch (ch)
	    {
	    case '[':
		this.machine.switchState("value-started");
		break;
	    case ';':
		this.machine.switchState("node-started");
		break;
	    case '(':
		this.machine.switchState("tree-started");
		break;
	    case ')':
		this.machine.switchState("tree-ended");
		break;
	    default:
		if (ch >= 'A' && ch <='Z')
		{
		    this.machine.bufferChar(ch);
		    this.machine.switchState("prop-started");
		    break;
		}
		else if (this.isWhiteSpace(ch))
		{
		    // ignore white space
		    break;
		}
		else
		{
		    this.reportError("fatal", "Unexpected Character");
		}
	    }
	}
    });